package ar.fi.uba.celdas.interfaces.bc;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import ar.fi.uba.celdas.interfaces.ambiente.IAccion;

public class Regla implements Cloneable, Comparable<Regla> {
	
	private Collection<Sensor> condiciones = new ArrayList<Sensor>();
	private Collection<Sensor> condicionesNegadas = new ArrayList<Sensor>();
	private List<IAccion> acciones = new ArrayList<IAccion>();
	private Collection<Sensor> predicciones = new ArrayList<Sensor>();
	private int exitos = 0;
	private int uso = 0;
	private int utilidad = 0;

	public Regla() {
	}

	public Collection<Sensor> getCondiciones() {
		return condiciones;
	}

	public Collection<Sensor> getCondicionesNegadas() {
		return condicionesNegadas;
	}

	public List<IAccion> getAcciones() {
		return acciones;
	}

	public Collection<Sensor> getPredicciones() {
		return predicciones;
	}

	public int getExitos() {
		return exitos;
	}

	public void setExitos(int exitos) {
		this.exitos = exitos;
	}
	
	public int getUso() {
		return uso;
	}

	public void setUso(int uso) {
		this.uso = uso;
	}
	
	/**
	 * Registra la correcta ejecución de la regla y devuelve el cociente de
	 * éxito (usos exitosos / usos totales)
	 * 
	 * @return el cociente entre la cantidad de usos exitosos y usos totales.
	 */
	public double registrarUsoExitoso() {
		
		//uso++;
		exitos++;
		
		return getCocienteExito();
	}
	
	public double registrarUso() {
		uso++;
		return getCocienteExito();
	}
	
	public double registrarUsoNoExistoso() {
		
		uso++;
		return getCocienteExito();
	}

	public double getCocienteExito() {
		
		return (double)exitos / (double)uso;
	}
	
	public int getUtilidad() {
		return utilidad;
	}

	public void setUtilidad(int utilidad) {
		this.utilidad = utilidad;
	}

	@Override
	public Object clone() throws CloneNotSupportedException {
		final Regla nueva = new Regla();
		nueva.acciones.addAll(acciones);
		nueva.condiciones.addAll(condiciones);
		nueva.condicionesNegadas.addAll(condicionesNegadas);
		nueva.predicciones.addAll(predicciones);
		return nueva;
	}

	public int compareTo(Regla r) {
		return (r.getUtilidad() - this.utilidad);
	}

	@Override
	public boolean equals(Object other) {

		if (other == null) {
			return false;
		}

		if (other == this) {
			return true;
		}

		if (!(other instanceof Regla)) {
			return false;
		}

		Regla otraRegla = (Regla) other;

		// Debe tener las mismas condiciones
		if (this.getCondiciones() == null && otraRegla.getCondiciones() != null) {
			return false;
		}

		if (this.getCondiciones() != null && otraRegla.getCondiciones() == null) {
			return false;
		}

		if (this.getCondiciones() != null && otraRegla.getCondiciones() != null) {

			if (!this.getCondiciones().containsAll(otraRegla.getCondiciones())) {

				return false;
			}
		}

		// Debe tener las mismas condiciones negadas
		if (this.getCondicionesNegadas() == null
				&& otraRegla.getCondicionesNegadas() != null) {
			return false;
		}

		if (this.getCondicionesNegadas() != null
				&& otraRegla.getCondicionesNegadas() == null) {
			return false;
		}

		if (this.getCondicionesNegadas() != null
				&& otraRegla.getCondicionesNegadas() != null) {

			if (!this.getCondicionesNegadas().containsAll(
					otraRegla.getCondicionesNegadas())) {

				return false;
			}
		}

		// Debe tener las mismas acciones
		if (this.getAcciones() == null && otraRegla.getAcciones() != null) {
			return false;
		}

		if (this.getAcciones() != null && otraRegla.getAcciones() == null) {
			return false;
		}

		if (this.getAcciones() != null && otraRegla.getAcciones() != null) {

			if (!this.getAcciones().containsAll(otraRegla.getAcciones())) {

				return false;
			}
		}

		// Debe tener las mismas predicciones
		if (this.getPredicciones() == null
				&& otraRegla.getPredicciones() != null) {
			return false;
		}

		if (this.getPredicciones() != null
				&& otraRegla.getPredicciones() == null) {
			return false;
		}

		if (this.getPredicciones() != null
				&& otraRegla.getPredicciones() != null) {

			if (!this.getPredicciones()
					.containsAll(otraRegla.getPredicciones())) {

				return false;
			}
		}

		return true;
	}
	
	/**
	 * Devuelve <b>true</b> sólo si la regla dada es igual a esta regla.
	 * Más específicamente, si <code>Regla.equals()</code> es <b>true</b>
	 * 
	 * @param condiciones el conjunto de condiciones a comparar 
	 * contra los de esta regla
	 * @return <b>true</b> sólo si la regla dada es igual a esta regla. 
	 * <b>false</b> en cualquier otro caso.
	 */
	public boolean matchPerfecto(Regla regla) {
		
		return this.equals(regla);
	}
	
	/**
	 * Devuelve <b>true</b> si las condiciones de la regla dada son un 
	 * subconjunto de las condiciones de esta regla o, si las condiciones 
	 * de esta regla son un subconjunto de las condiciones de la regla dada 
	 * y aquellas condiciones de la regla dada que no formen parte de esta 
	 * regla no están en el conjunto de condiciones negadas.
	 * 
	 * @param regla la regla a comparar
	 * @return <b>true</b> si las condiciones de la regla dada son un 
	 * subconjunto de las condiciones de esta regla o, si las condiciones 
	 * de esta regla son un subconjunto de las condiciones de la regla dada 
	 * y aquellas condiciones de la regla dada que no formen parte de esta 
	 * regla no están en el conjunto de condiciones negadas. <b>false</b> en 
	 * cualquier otro caso.
	 */
	// TODO: debe matchear por las condiciones, las condiciones negadas, las acciones y las predicciones
	// TODO: debe ser capaz de interpretar la no existencia de condiciones como *any*
	public boolean match(Regla regla) {
		
		boolean match = false;
		
		Collection<Sensor> condiciones = regla.getCondiciones();
		
		if (condiciones != null) {
			
			if (condiciones.containsAll(this.getCondiciones())) {
				
				Iterator<Sensor> iteradorCondiciones = condiciones.iterator();
				boolean existeCondicionNegada = false;
				
				while (iteradorCondiciones.hasNext() && !existeCondicionNegada) {
					
					Sensor condicion = iteradorCondiciones.next();
					
					if (this.getCondicionesNegadas().contains(condicion)) {
						
						existeCondicionNegada = true;
					}
				}
				
				if (!existeCondicionNegada) {
					
					match = true;
				}
			}
			
			if (this.getCondiciones().containsAll(condiciones)) {
				
				match = true;
			}
		}
		
		return match;
	}

	public boolean matchCondiciones(Collection<Sensor> estado) {
		
		Collection<Sensor> condiciones = this.getCondiciones();
		Collection<Sensor> condicionesNegadas = this.getCondicionesNegadas();
		
		if (condiciones != null) {
			//Compruebo condiciones
			if (estado.containsAll(condiciones)) {
				//si se cumplen las condiciones compruebo que no se cumplan las condiciones negadas
				if (condicionesNegadas != null) {
					for (Sensor condicion : estado) {
						if (condicionesNegadas.contains(condicion)) {
							return false;
						}
					}
				}
				return true;
			}
		}

		return false;
	}
	
	public boolean matchCondiciones(Regla regla) {
		
		Collection<Sensor> condiciones1 = this.getCondiciones();
		Collection<Sensor> condicionesNegadas1 = this.getCondicionesNegadas();
		
		Collection<Sensor> condiciones2 = regla.getCondiciones();
		Collection<Sensor> condicionesNegadas2 = regla.getCondicionesNegadas();
		
		if (condiciones1.containsAll(condiciones2) && condicionesNegadas1.containsAll(condicionesNegadas2)) {
			return true;
		}
		return false;
	}
	
	public boolean matchCondicionesAcciones(Regla regla) {
		
		List<IAccion> acciones1 = this.getAcciones();
		List<IAccion> acciones2 = regla.getAcciones();
		
		if (matchCondiciones(regla) && acciones1.containsAll(acciones2)) {
			return true;
		}
		return false;
	}
	
	public boolean matchCondicionesAccionesPredicciones(Regla regla) {
		
		Collection<Sensor> predicciones1 = this.getPredicciones();
		Collection<Sensor> predicciones2 = regla.getPredicciones();
		
		if (matchCondicionesAcciones(regla) && predicciones1.containsAll(predicciones2)) {
			return true;
		}
		return false;
	}
	
	public boolean matchPredicciones(Collection<Sensor> estado) {
		
		Collection<Sensor> predicciones = this.getPredicciones();
		
		return estado.containsAll(predicciones);
	}
	
	// TODO: reescribir usando un StringBuffer o, mejor aún, escribir un toString
	// más simple y usar un Renderer de Log4J para verlo en los logs.
	public String toString() {
		
		return "Regla[Condiciones:" + condiciones + ",Negadas:" 
		+ condicionesNegadas + ",Predicciones:" + predicciones 
		+ ",Acciones:" + acciones + ",Uso:" + uso + ",Exitos:" 
		+ exitos + ",Utilidad:" + utilidad + "]";
	}
}
